package cn.yeziji.forum.utils;

import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;
import org.yaml.snakeyaml.util.UriEncoder;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 字符串工具类
 *
 * @author gzkemays
 * @since 2021/11/18 17:54
 */
public class StrUtils {
  private static final String HTTP = "http";
  private static final String HTTPS = "https";
  private static final String WWW = "www.";
  // 最大的 ASCII 码
  private static final int MAX_ASCII = 123;
  // 最小的 ASCII 码
  private static final int MIN_ASCII = 91;
  // 中间 ASCII 码
  private static final int MID_ASCII = 96;
  /** 英文过滤 */
  private static final Pattern EN_PATTERN = Pattern.compile("([A-Za-z]+)");

  /**
   * 是否为 web
   *
   * @param url 地址
   * @return 判断结果
   */
  public static boolean isWeb(String url) {
    return (url.startsWith(HTTP) || url.startsWith(HTTPS) || url.startsWith(WWW));
  }

  /**
   * list 转 str
   *
   * @param list list 列表
   * @param separator 分隔符
   * @param <T> 泛型
   * @return str
   */
  public static <T> String listToStr(List<T> list, CharSequence separator) {
    StringJoiner sj = new StringJoiner(separator);
    for (Object o : list) {
      sj.add(String.valueOf(o));
    }
    return sj.toString();
  }

  @SuppressWarnings("unchecked")
  public static <T> List<T> strToList(String str, String sequence, Class<T> clazz) {
    if (StringUtils.hasText(str)) {
      String[] split = str.split(sequence);
      return Arrays.stream(split).map(s -> (T) parse(s, clazz)).collect(Collectors.toList());
    }
    return new ArrayList<>();
  }

  public static String dateToStr(String pattern, Date date) {
    if (date == null) {
      return null;
    }
    SimpleDateFormat sdf = new SimpleDateFormat(pattern);
    return sdf.format(date);
  }

  public static String getLastSplitHasSplitAfterStr(String str, String split) {
    return getLastSplitAfterStr(str, split, true);
  }

  public static String getLastSplitAfterStr(String str, String split) {
    return getLastSplitAfterStr(str, split, false);
  }

  public static boolean isEmail(String str) {
    return str.matches("[\\w\\.\\-]+@([\\w\\-]+\\.)+[\\w\\-]+")
        && org.apache.commons.lang3.StringUtils.isNotBlank(str);
  }

  /**
   * 获取 split 后续文字
   *
   * @param str 字符串
   * @param split 指定符号
   * @return 后续文字
   */
  private static String getLastSplitAfterStr(String str, String split, boolean hasSplit) {
    if (str != null && str.length() > 0) {
      int splitIndex = hasSplit ? str.lastIndexOf(split) : str.lastIndexOf(split) + 1;
      if (splitIndex > -1 && splitIndex < str.length() - 1) {
        return str.substring(splitIndex);
      }
    }
    return null;
  }

  /**
   * 基本类型的转换
   *
   * @param str 字符串
   * @param clazz 基本类型
   * @return 转换数据
   * @param <T> 泛型
   */
  private static <T> Object parse(String str, Class<T> clazz) {
    if (clazz.isAssignableFrom(Integer.class)) {
      return Integer.parseInt(str);
    }
    if (clazz.isAssignableFrom(Boolean.class)) {
      return Boolean.parseBoolean(str);
    }
    if (clazz.isAssignableFrom(Long.class)) {
      return Long.parseLong(str);
    }
    if (clazz.isAssignableFrom(Double.class)) {
      return Double.parseDouble(str);
    }
    if (clazz.isAssignableFrom(Float.class)) {
      return Float.parseFloat(str);
    }
    if (clazz.isAssignableFrom(Short.class)) {
      return Short.parseShort(str);
    }
    clazz.isAssignableFrom(String.class);
    return str;
  }

  /**
   * 简单时间戳字符串加密
   *
   * <p>可作用于前后端加密通信
   *
   * <pre lange="js">
   *        // 最大的 ASCII 码
   *        const MAX_ASCII = 123;
   *        // 最小的 ASCII 码
   *        const MIN_ASCII = 91;
   *        // 中间 ASCII 码
   *        const MID_ASCII = 96;
   *        // 前端字符串与字节相互转换的方法
   *        function stringToBytes(str) {
   *            let bytes = [];
   *            let len, c;
   *            len = str.length;
   *            for (let i = 0; i < len; i++) {
   *                c = str.charCodeAt(i);
   *                if (c >= 0x010000 && c <= 0x10FFFF) {
   *                    bytes.push(((c >> 18) & 0x07) | 0xF0);
   *                    bytes.push(((c >> 12) & 0x3F) | 0x80);
   *                    bytes.push(((c >> 6) & 0x3F) | 0x80);
   *                    bytes.push((c & 0x3F) | 0x80);
   *                } else if (c >= 0x000800 && c <= 0x00FFFF) {
   *                    bytes.push(((c >> 12) & 0x0F) | 0xE0);
   *                    bytes.push(((c >> 6) & 0x3F) | 0x80);
   *                    bytes.push((c & 0x3F) | 0x80);
   *                } else if (c >= 0x000080 && c <= 0x0007FF) {
   *                    bytes.push(((c >> 6) & 0x1F) | 0xC0);
   *                    bytes.push((c & 0x3F) | 0x80);
   *                } else {
   *                    bytes.push(c & 0xFF);
   *                }
   *            }
   *            return bytes;
   *        }
   *
   *         function byteToString(arr) {
   *            if (typeof arr === 'string') {
   *                return arr;
   *            }
   *            let str = '',
   *                _arr = arr;
   *            for (let i = 0; i < _arr.length; i++) {
   *                let one = _arr[i].toString(2),
   *                    v = one.match(/^1+?(?=0)/);
   *                if (v && one.length == 8) {
   *                    let bytesLength = v[0].length;
   *                    let store = _arr[i].toString(2).slice(7 - bytesLength);
   *                    for (let st = 1; st < bytesLength; st++) {
   *                        store += _arr[st + i].toString(2).slice(2);
   *                    }
   *                    str += String.fromCharCode(parseInt(store, 2));
   *                    i += bytesLength - 1;
   *                } else {
   *                    str += String.fromCharCode(_arr[i]);
   *                }
   *            }
   *            return str;
   *          }
   * </pre>
   *
   * @param str 字符串
   * @param timestamp 时间戳
   * @return 加密字段
   */
  public static String enStr(String str, String timestamp) {
    StringBuilder newStr = new StringBuilder();
    int j = 0;
    for (int i = 0; i < str.length(); i++) {
      String tempStr = String.valueOf(str.charAt(i));
      if (j > timestamp.length() - 1) {
        j = 0;
      }

      System.out.println(EN_PATTERN.matcher(tempStr).matches());
      if (EN_PATTERN.matcher(tempStr).matches()) {
        int strBytes = str.charAt(i);
        int num = strBytes + (timestamp.charAt(j) - '0');
        String enStr;
        // 由字母转成符号的话需要进行标记，标记为 (tse, newChar, oldChar)
        if ((num >= MIN_ASCII && num <= MID_ASCII) || num >= MAX_ASCII) {
          enStr = "(^tse, " + tempStr + ", " + num + "$)";
        } else {
          enStr = String.valueOf((char) num);
        }
        newStr.append(enStr);
        j++;
      } else {
        newStr.append(tempStr);
      }
    }
    return Base64Utils.encodeToString(UriEncoder.encode(newStr.toString()).getBytes());
  }

  /**
   * 对应 {@link #enStr(String, String)} 进行解密
   *
   * <p style="color: red">确保 timestamp 相同
   *
   * @param str 加密字符串
   * @param timestamp 时间戳
   * @return 解密结果
   */
  public static String deStr(String str, String timestamp) {
    str = UriEncoder.decode(new String(Base64Utils.decodeFromString(str)));
    System.out.println("str = " + str);
    StringBuilder newStr = new StringBuilder();
    int j = 0;
    for (int i = 0; i < str.length(); i++) {
      String tempStr = String.valueOf(str.charAt(i));
      if (j > timestamp.length() - 1) {
        j = 0;
      }
      String marker = null;
      // 确保标记长度
      if (str.length() - i > 6) {
        marker = str.substring(i, i + 5);
      }
      if (marker != null && marker.startsWith("(^tse")) {
        int k = i;
        String end = "";
        StringBuilder markerStr = new StringBuilder();
        while (!end.equals("$)")) {
          markerStr.append(str.charAt(k));
          end = str.substring(k, k + 2);
          k++;
        }
        String newMarker = markerStr.toString().replaceFirst("\\(", "");
        String[] markerArr = newMarker.split(",");
        newStr.append(markerArr[1].trim());
        // 跳跃定位
        i = k;
        j++;
      } else if (EN_PATTERN.matcher(tempStr).matches()) {
        int strBytes = str.charAt(i);
        int num = strBytes - (timestamp.charAt(j) - '0');
        newStr.append((char) num);
        j++;
      } else {
        newStr.append(tempStr);
      }
    }
    return newStr.toString();
  }

  public static void main(String[] args) {
    String txt =
        "JTdCJTIybGsoJTVFdHNlJTJDJTIweSUyQyUyMDEyNiUyNCklMjIlM0ElMjIoJTVFdHNlJTJDJTIwdSUyQyUyMDEyNCUyNClzZnVOeGwlMjIlMkMlMjIoJTVFdHNlJTJDJTIwdiUyQyUyMDEyMyUyNClhc3ZrJTIyJTNBJTIyJTdCJTVDJTIycWx2Zm8lNUMlMjIlM0ElNUMlMjIwJTVDJTIyJTJDJTVDJTIyb25ocG5obmslNUMlMjIlM0ElNUMlMjIlRTQlQkQlQTAlRTglQUYlQjQlRTglQkYlODclRTclODMlOUYlRTglOEElQjElRTQlQjklOUYlRTUlOEYlQUIlRTQlQkQlOUMlRTclODElQUIlRTglOEElQjElNUMlMjIlMkMlNUMlMjJmKCU1RXRzZSUyQyUyMHYlMkMlMjAxMjUlMjQpYXVkcyU1QyUyMiUzQSU1QyUyMm15eXB6JTNBJTJGJTJGanNsLiglNUV0c2UlMkMlMjB5JTJDJTIwMTI4JTI0KWUoJTVFdHNlJTJDJTIweiUyQyUyMDEyMyUyNClsa24uKCU1RXRzZSUyQyUyMHglMkMlMjAxMjUlMjQpKCU1RXRzZSUyQyUyMHklMkMlMjAxMjYlMjQpeiUyRiglNUV0c2UlMkMlMjB1JTJDJTIwMTI0JTI0KXRrdyUyRmh2YndidyUyRjY3NzJrMTRmMjA0NzM2YzJrOGQ1NWhnbDFkZDM2ODY5Lm1xbCU1QyUyMiUyQyU1QyUyMmpyYXBtJTVDJTIyJTNBJTVDJTIyMTUzODIxMTI4MSU0MHd2LmpvbiU1QyUyMiUyQyU1QyUyMmdmeGglNUMlMjIlM0ElNUMlMjIlRTUlQTUlQkQlRTUlQTUlQkQlRTUlOEElQUElRTUlOEElOUIlRUYlQkMlOEMlRTQlQjglOEIlRTUlQTUlQkQlRTYlQUYlOEYlRTQlQjglODAlRTklQTElQkYlRTklQTUlQUQlRTMlODAlODIlNUMlMjIlMkMlNUMlMjJ6c2xzdGZ0ZSU1QyUyMiUzQSU1QyUyMmgoJTVFdHNlJTJDJTIweiUyQyUyMDEyNSUyNClsanJmeXolNUMlMjIlN0QlMjIlN0Q=";
    String timestamp = "1657013155507";
    String json =
        "{\"key\":\"userMsg\",\"value\":\"{\\\"level\\\":\\\"0\\\",\\\"nickname\\\":\\\"你说过烟花也叫作火花\\\",\\\"avatar\\\":\\\"https://img.yeziji.xyz/user/avatar/6772f14a204736c2d8c55bbe1dc36869.jpg\\\",\\\"email\\\":\\\"1538211281@qq.com\\\",\\\"desc\\\":\\\"好好努力，下好每一顿饭。\\\",\\\"username\\\":\\\"gzkemays\\\"}\"}";
    String enStr;
    System.out.println(json);
    System.out.println((enStr = enStr(json, timestamp)));
    System.out.println(deStr(txt, timestamp));
    System.out.println(deStr(enStr, timestamp));
  }
}
